#include <cstring>
#include <cstdlib>
#include <algorithm>
#include "svd_data.h"

class ReDataLoader: public IDataIterator<ReData::Sample> {
private:
	FILE *fi;
	float scale_score;
	char name_data[256];
	std::vector<unsigned> index;
	std::vector<float> value;
public:
	ReDataLoader() {
		fi = NULL;
		scale_score = 1.0f;
		strcpy(name_data, "NULL");
	}
	virtual ~ReDataLoader() {
		if (fi != NULL)
			fclose(fi);
	}
	virtual void set_param(const char *name, const char *val) {
		if (!strcmp(name, "scale_score"))
			scale_score = (float) atof(val);
		if (!strcmp(name, "data_in"))
			strcpy(name_data, val);
	}
	virtual void init(void) {
		fi = fopen(name_data, "r");
	}
	virtual bool next(ReData::Sample &e) {
		if (fscanf(fi, "%f%d%d%d", &e.label, &e.num_global, &e.num_ufactor, &e.num_ifactor) != 4)
			return false;
		e.label /= scale_score;
		int n = e.total_num();
		index.resize(static_cast<size_t>(n));
		value.resize(static_cast<size_t>(n));
		e.set_space(&index[0], &value[0]);

		for (int i = 0; i < n; i++) {
			if (fscanf(fi, "%d:%f", &e.index_global[i], &e.value_global[i]) != 2) {
				fprintf(stderr, "error loading line=%d\n", i);
				info("error");
			}
		}
		return true;
	}
	virtual void before_first(void) {
		fseek(fi, 0, SEEK_SET);
	}
};

// input structure for SVD++ style implict explict feedback
// loader that load SVDPlus block from text data
class ReDataBlockLoader: public IDataIterator<ReDataBlock> {
private:
// maximum number of lines that can be fit into a block
	int block_max_line;
// number of remaining lines to be loaded
	int nline_remain;
private:
	FILE *fi, *finfo;
	char name_data[256];
	char name_info[256];
private:
	std::vector<unsigned> index_ufeedback;
	std::vector<float> value_ufeedback;
private:
	std::vector<float> row_label;
	std::vector<int> row_ptr;
	std::vector<unsigned> feat_index;
	std::vector<float> feat_value;
private:
	struct Sample {
		unsigned index;
		float value;
		inline bool operator<(const Sample &b) const {
			return index < b.index;
		}
	};
	inline void load(std::vector<Sample> &vec, int n) {
		Sample e;
		vec.clear();
		while (n--) {
			assert(fscanf(fi, "%d:%f", &e.index, &e.value) == 2);
			vec.push_back(e);
		}
		sort(vec.begin(), vec.end());
	}
	inline void add(const std::vector<Sample> &vec) {
		for (size_t i = 0; i < vec.size(); i++) {
			feat_index.push_back(vec[i].index);
			feat_value.push_back(vec[i].value);
		}
	}
private:
// do buffer creation with only fi, a bit duplicate code for correctness
	float olabel;
	std::vector<Sample> og, ou, oi;
	inline bool next_onlyfi(ReDataBlock &e) {
		if (ou.size() == 0) {
			int ng, nu, ni;
			if (fscanf(fi, "%f%d%d%d", &olabel, &ng, &nu, &ni) != 4)
				return false;
			this->load(og, ng);
			this->load(ou, nu);
			this->load(oi, ni);
		}
		index_ufeedback.clear();
		value_ufeedback.clear();
		e.num_ufeedback = 0;
		e.extend_tag = ReDataBlock::DEFAULT;
		e.index_ufeedback = NULL;
		e.value_ufeedback = NULL;

		unsigned uid = ou[0].index;

		if (this->nline_remain != 0) {
			int start = (block_max_line + 1) / 2;
			int nrow = static_cast<int>(row_label.size()) - start;
			int rstart = row_ptr[start * 3];

			memmove(&row_label[0], &row_label[start], sizeof(float) * nrow);
			memmove(&feat_index[0], &feat_index[rstart], sizeof(unsigned) * (row_ptr.back() - rstart));
			memmove(&feat_value[0], &feat_value[rstart], sizeof(float) * (row_ptr.back() - rstart));
			for (size_t i = start * 3; i < row_ptr.size(); i++) {
				row_ptr[i - start * 3] = row_ptr[i] - rstart;
			}
			row_label.resize(nrow);
			row_ptr.resize(nrow * 3 + 1);
			feat_index.resize(row_ptr.back());
			feat_value.resize(row_ptr.back());
		} else {
			row_label.resize(0);
			row_ptr.resize(0);
			row_ptr.push_back(0);
			feat_index.clear();
			feat_value.clear();
		}

		{
			row_label.push_back(olabel / scale_score);
			row_ptr.push_back(row_ptr.back() + static_cast<int>(og.size()));
			row_ptr.push_back(row_ptr.back() + static_cast<int>(ou.size()));
			row_ptr.push_back(row_ptr.back() + static_cast<int>(oi.size()));
			this->add(og);
			this->add(ou);
			this->add(oi);
			og.clear();
			ou.clear();
			oi.clear();
		}
		this->nline_remain = 0;
		int ng, nu, ni;
		while (fscanf(fi, "%f%d%d%d", &olabel, &ng, &nu, &ni) == 4) {
			std::vector<Sample> vg, vu, vi;
			this->load(vg, ng);
			this->load(vu, nu);
			this->load(vi, ni);
			assert(vu.size() != 0);
			if (vu[0].index != uid) {
				og = vg;
				ou = vu;
				oi = vi;
				break;
			}
			if (row_label.size() >= static_cast<size_t>(block_max_line)) {
				this->nline_remain = 1;
				og = vg;
				ou = vu;
				oi = vi;
				break;
			}
			row_label.push_back(olabel / scale_score);
			row_ptr.push_back(row_ptr.back() + ng);
			row_ptr.push_back(row_ptr.back() + nu);
			row_ptr.push_back(row_ptr.back() + ni);
			this->add(vg);
			this->add(vu);
			this->add(vi);
		}
		if (this->nline_remain != 0) {
			e.data.num_row = (block_max_line + 1) / 2;
		} else {
			e.data.num_row = static_cast<int>(row_label.size());
		}
		e.data.num_val = row_ptr[e.data.num_row * 3];
		e.data.row_ptr = &row_ptr[0];
		e.data.row_label = &row_label[0];
		e.data.feat_index = &feat_index[0];
		e.data.feat_value = &feat_value[0];
		return true;
	}
public:
	float scale_score;
	ReDataBlockLoader() {
		scale_score = 1.0f;
		strcpy(name_info, "NULL");
		fi = NULL;
		finfo = NULL;
		this->block_max_line = 10000;
	}
	virtual ~ReDataBlockLoader() {
		if (fi != NULL)
			this->close();
	}
	virtual void init(void) {
		this->open(name_data, name_info);
		this->nline_remain = 0;
	}
	virtual void set_param(const char *name, const char *val) {
		if (!strcmp(name, "scale_score"))
			scale_score = (float) atof(val);
		if (!strcmp(name, "data_in"))
			strcpy(name_data, val);
		if (!strcmp(name, "feedback_in"))
			strcpy(name_info, val);
		if (!strcmp(name, "block_max_line"))
			block_max_line = atoi(val);
	}
// load data into e, caller isn't responsible for space free
// Loader will keep the space until next call of this function
	virtual bool next(ReDataBlock &e) {
		if (finfo == NULL)
			return next_onlyfi(e);

		e.extend_tag = ReDataBlock::MIDDLE_TAG;
		// load from feedback file
		if (nline_remain == 0) {
			int num_ufeedback;
			if (fscanf(finfo, "%d%d", &nline_remain, &num_ufeedback) != 2)
				return false;
			index_ufeedback.resize(static_cast<size_t>(num_ufeedback));
			value_ufeedback.resize(static_cast<size_t>(num_ufeedback));
			for (int i = 0; i < num_ufeedback; i++) {
				assert(fscanf(finfo, "%d:%f", &index_ufeedback[i], &value_ufeedback[i]) == 2);
			}
			e.extend_tag = e.extend_tag & ReDataBlock::START_TAG;
		}

		// check lines to be loaded
		int num_line = nline_remain;
		if (nline_remain > block_max_line) {
			// smart arrangement to make data in similar size in each block
			int pc = (nline_remain + block_max_line - 1) / block_max_line;
			num_line = (nline_remain + pc - 1) / pc;
		} else {
			e.extend_tag = e.extend_tag & ReDataBlock::END_TAG;
		}
		this->nline_remain -= num_line;
		// set data
		if (e.extend_tag = ReDataBlock::MIDDLE_TAG) {
			e.num_ufeedback = 0;
			e.index_ufeedback = NULL;
			e.value_ufeedback = NULL;
		} else {
			e.num_ufeedback = static_cast<int>(index_ufeedback.size());
			if (index_ufeedback.size() != 0) {
				e.index_ufeedback = &index_ufeedback[0];
				e.value_ufeedback = &value_ufeedback[0];
			}
		}

		int num_elem = 0;
		row_label.resize(static_cast<size_t>(num_line));
		row_ptr.resize(static_cast<size_t>(num_line * 3 + 1));
		row_ptr[0] = 0;
		feat_index.clear();
		feat_value.clear();
		for (int i = 0; i < num_line; i++) {
			int ng, nu, ni;
			assert(fscanf(fi, "%f%d%d%d", &row_label[i], &ng, &nu, &ni) == 4);
			row_label[i] /= scale_score;
			row_ptr[i * 3 + 1] = (num_elem += ng);
			row_ptr[i * 3 + 2] = (num_elem += nu);
			row_ptr[i * 3 + 3] = (num_elem += ni);
			std::vector<Sample> vg, vu, vi;
			this->load(vg, ng);
			this->add(vg);
			this->load(vu, nu);
			this->add(vu);
			this->load(vi, ni);
			this->add(vi);
		}
		e.data.num_row = num_line;
		e.data.num_val = num_elem;
		e.data.row_ptr = &row_ptr[0];
		e.data.row_label = &row_label[0];
		e.data.feat_index = &feat_index[0];
		e.data.feat_value = &feat_value[0];

		return true;
	}
	virtual void before_first() {
		fseek(fi, 0, SEEK_SET);
		if (finfo != NULL)
			fseek(finfo, 0, SEEK_SET);
		og.clear();
		ou.clear();
		oi.clear();
		this->nline_remain = 0;
	}
	inline void close() {
		if (fi != NULL)
			fclose(fi);
		if (finfo != NULL)
			fclose(finfo);
		this->fi = NULL;
		this->finfo = NULL;
	}
	inline void open(const char *fname, const char *fname_info) {
		fi = fopen(fname, "r");
		if (strcmp(fname_info, "NULL")) {
			finfo = fopen(fname_info, "r");
		} else {
			finfo = NULL;
		}
	}
};
